/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
using System;

namespace LhaUtil
{
    /// <summary>
    /// The LhaInputStream reads a LHa archive as an InputStream.
    /// methods are provided to position at each successive entry in
    /// the archive, and the read each entry as a normal input stream
    /// using read().
    /// 
    /// LhaInputStream is based on ZipInputStream.cs.
    /// ZipInputStream copyright is as follows.
    /// Copyright (C) 2001 Mike Krueger
    /// Copyright (C) 2004 John Reilly
    /// </summary>
    public class LhaInputStream : System.IO.Stream
    {
#region Constructors
        /// <summary>
        /// Construct a LhaInputStream
        /// </summary>
        /// <param name="inputStream">stream to source data from</param>
        public LhaInputStream(System.IO.Stream inputStream)
        {
            this.inputStream = inputStream;
        }
#endregion

#region Stream Overrides
        /// <summary>
        /// Gets a value indicating whether the current stream supports reading
        /// </summary>
        public override bool CanRead
        {
            get { return inputStream.CanRead; }
        }

        /// <summary>
        /// Gets a value indicating whether the current stream supports seeking
        /// This property always returns false.
        /// </summary>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// Gets a value indicating if the stream supports writing.
        /// This property always returns false.
        /// </summary>
        public override bool CanWrite
        {
            get { return false; }
        }

        /// <summary>
        /// The length in bytes of the stream
        /// </summary>
        public override long Length
        {
            get { return inputStream.Length; }
        }

        /// <summary>
        /// Gets or sets the position within the stream. 
        /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
        /// </summary>
        /// <exception cref="NotSupportedException">Any attempt to set position</exception>
        public override long Position
        {
            get { return inputStream.Position; }
            set { throw new System.NotSupportedException(); }
        }

        /// <summary>
        /// Flushes the baseInputStream
        /// </summary>
        public override void Flush()
        {
            inputStream.Flush();
        }

        /// <summary>
        /// Set the streams position.  This operation is not supported and will throw a NotSupportedException
        /// </summary>
        /// <exception cref="NotSupportedException">Any access</exception>
        public override long Seek(long offset, System.IO.SeekOrigin origin)
        {
            throw new System.NotSupportedException();
        }

        /// <summary>
        /// Sets the length of the stream
        /// This operation is not supported and will throw a NotSupportedException
        /// </summary>
        /// <exception cref="NotSupportedException">Any access</exception>
        public override void SetLength(long value)
        {
            throw new System.NotSupportedException();
        }

        /// <summary>
        /// Writes a block of bytes to this stream using data from a buffer.
        /// This operation is not supported and will throw a NotSupportedException
        /// </summary>
        /// <exception cref="NotSupportedException">Any access</exception>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new System.NotSupportedException();
        }

        /// <summary>
        /// Writes a byte to the current position in the file stream.
        /// This operation is not supported and will throw a NotSupportedException
        /// </summary>
        /// <exception cref="NotSupportedException">Any access</exception>
        public override void WriteByte(byte value)
        {
            throw new System.NotSupportedException();
        }

        /// <summary>
        /// Reads a byte from the current tar archive entry.
        /// This method simply calls Read(byte[], int, int).
        /// </summary>
        public override int ReadByte()
        {
            byte[] oneByteBuffer = new byte[1];
            int num = this.Read(oneByteBuffer, 0, 1);
            if (num <= 0) { // return -1 to indicate that no byte was read.
                return -1;
            }
            return oneByteBuffer[0];
        }

        private byte[] decodedData;
        private int decodedDataReadPos;

        /// <summary>
        /// Reads bytes from the current tar archive entry.
        /// 
        /// This method is aware of the boundaries of the current
        /// entry in the archive and will deal with them appropriately
        /// </summary>
        /// <param name="buffer">
        /// The buffer into which to place bytes read.
        /// </param>
        /// <param name="offset">
        /// The offset at which to place bytes read.
        /// </param>
        /// <param name="count">
        /// The number of bytes to read.
        /// </param>
        /// <returns>
        /// The number of bytes read, or 0 at end of stream/EOF.
        /// </returns>
        public override int Read(byte[] buffer, int offset, int count)
        {
            try
            {
                if (buffer == null)
                {
                    throw new System.ArgumentNullException("buffer");
                }
                if (null == currentEntry)
                {
                    return 0;
                }
                switch (currentEntry.CompressMethod)
                {
                    case LzhCompressMethodType.LH4:
                    case LzhCompressMethodType.LH5:
                    case LzhCompressMethodType.LH6:
                    case LzhCompressMethodType.LH7:
                        if (!currentEntry.Decoded)
                        {
                            DecodeLzHuf();
                            currentEntry.Decoded = true;
                        }
                        if (decodedData.Length - decodedDataReadPos < count)
                        {
                            count = decodedData.Length - decodedDataReadPos;
                        }
                        System.Array.Copy(decodedData, decodedDataReadPos, buffer, offset, count);
                        decodedDataReadPos += count;
                        return count;
                    case LzhCompressMethodType.LH0:
                    case LzhCompressMethodType.LZ4:
                    default:
                        if (bytesToTheNextHeader < count)
                        {
                            count = bytesToTheNextHeader;
                        }
                        return ReadRaw(buffer, offset, count);
                }
            }
            catch (System.IndexOutOfRangeException)
            {
                throw new System.IO.InvalidDataException();
            }
            catch (System.OverflowException)
            {
                throw new System.IO.InvalidDataException();
            }
        }

        /// <summary>
        /// Closes this stream.
        /// </summary>
        public override void Close()
        {
            this.inputStream.Close();
        }
        public int BytesToTheNextHeader
        {
            get { return bytesToTheNextHeader; }
        }
        public int ReadRaw(byte[] buffer, int offset, int count)
        {
            if (buffer == null) {
                throw new System.ArgumentNullException("buffer");
            }

            int readBytes = inputStream.Read(buffer, offset, count);
            bytesToTheNextHeader -= readBytes;
            return readBytes;
        }

        /// <summary>
        /// Get the next entry in this tar archive. This will skip
        /// over any remaining data in the current entry, if there
        /// is one, and place the input stream at the header of the
        /// next entry, and read the header and instantiate a new
        /// LhaEntry from the header bytes and return that entry.
        /// If there are no more entries in the archive, null will
        /// be returned to indicate that the end of the archive has
        /// been reached.
        /// </summary>
        /// <returns>
        /// The next LhaEntry in the archive, or null.
        /// </returns>
        public LhaEntry NextEntry()
        {
            try
            {
                if (this.currentEntry != null)
                {
                    if (!SkipToTheNextEntry())
                    {
                        return null;
                    }
                }

                LhaEntry entry = new LhaEntry(this);
                if (0 == entry.HeaderSize)
                {
                    entry = null;
                }
                this.currentEntry = entry;
                if (null != entry)
                {
                    bytesToTheNextHeader = entry.PackedDataSize;
                }
                return entry;
            } catch (System.IndexOutOfRangeException)
            {
                throw new System.IO.InvalidDataException();
            }
        }

        private bool SkipToTheNextEntry()
        {
            if (bytesToTheNextHeader < 0)
            {
                Console.WriteLine("E: SkipToTheNextEntry() bytesToTheNextHeader={0} Lha data corrupted!", bytesToTheNextHeader);
                return false;
            }
            inputStream.Seek(bytesToTheNextHeader, System.IO.SeekOrigin.Current);
            bytesToTheNextHeader = 0;
            return true;
        }
#endregion

#region Instance Fields
        /// <summary>
        /// Stream used as the source of input data.
        /// </summary>
        System.IO.Stream inputStream;

        /// <summary>
        /// current lha entry
        /// </summary>
        LhaEntry currentEntry;

        /// <summary>
        /// bytes to the next header
        /// </summary>
        int bytesToTheNextHeader;
#endregion

#region UnLHa
        // Following code is based on LHa for UNIX Ver. 1.14i.
        // Original copyright text is as follows.
        /* ------------------------------------------------------------------------ */
        /* LHa for UNIX                                                             */
        /*              lharc.c -- append to archive                                */
        /*                                                                          */
        /*      Copyright (C) MCMLXXXIX Yooichi.Tagawa                              */
        /*      Modified                Nobutaka Watazaki                           */
        /*                          Thanks to H.Yoshizaki. (MS-DOS LHarc)           */
        /*                                                                          */
        /*  Ver. 0.00  Original                         1988.05.23  Y.Tagawa        */
        /*  Ver. 0.01  Alpha Version (for 4.2BSD)       1989.05.28  Y.Tagawa        */
        /*  Ver. 0.02  Alpha Version Rel.2              1989.05.29  Y.Tagawa        */
        /*  Ver. 0.03  Release #3  Beta Version         1989.07.02  Y.Tagawa        */
        /*  Ver. 0.03a Debug                            1989.07.03  Y.Tagawa        */
        /*  Ver. 0.03b Modified                         1989.07.13  Y.Tagawa        */
        /*  Ver. 0.03c Debug (Thanks to void@rena.dit.junet)                        */
        /*                                              1989.08.09  Y.Tagawa        */
        /*  Ver. 0.03d Modified (quiet and verbose)     1989.09.14  Y.Tagawa        */
        /*  V1.00  Fixed                                1989.09.22  Y.Tagawa        */
        /*  V1.01  Bug Fixed                            1989.12.25  Y.Tagawa        */
        /*                                                                          */
        /*  DOS-Version Original LHx V C2.01        (C) H.Yohizaki                  */
        /*                                                                          */
        /*  V2.00  UNIX Lharc + DOS LHx -> OSK LHx      1990.11.01  Momozou         */
        /*  V2.01  Minor Modified                       1990.11.24  Momozou         */
        /*                                                                          */
        /*  Ver. 0.02  LHx for UNIX                     1991.11.18  M.Oki           */
        /*  Ver. 0.03  LHa for UNIX                     1991.12.17  M.Oki           */
        /*  Ver. 0.04  LHa for UNIX beta version        1992.01.20  M.Oki           */
        /*  Ver. 1.00  LHa for UNIX Fixed               1992.03.19  M.Oki           */
        /*                                                                          */
        /*  Ver. 1.10  for Symbolic Link                1993.06.25  N.Watazaki      */
        /*  Ver. 1.11  for Symbolic Link Bug Fixed      1993.08.18  N.Watazaki      */
        /*  Ver. 1.12  for File Date Check              1993.10.28  N.Watazaki      */
        /*  Ver. 1.13  Bug Fixed (Idicator calcurate)   1994.02.21  N.Watazaki      */
        /*  Ver. 1.13a Bug Fixed (Sym. Link delete)     1994.03.11  N.Watazaki      */
        /*  Ver. 1.13b Bug Fixed (Sym. Link delete)     1994.07.29  N.Watazaki      */
        /*  Ver. 1.14  Source All chagned               1995.01.14  N.Watazaki      */
        /*  Ver. 1.14b,c  Bug Fixed                     1996.03.07  t.okamoto       */
        /*  Ver. 1.14d Version up                       1997.01.12  t.okamoto       */
        /*  Ver. 1.14g Bug Fixed                        2000.05.06  t.okamoto       */
        /*  Ver. 1.14i Modified                         2000.10.06  t.okamoto       */
        /* ------------------------------------------------------------------------ */

        private const int THRESHOLD = 3;
        private byte[] packedData;
        private int packedDataPos;

        private void DecodeLzHuf()
        {
            packedData = new byte[currentEntry.PackedDataSize + 4];
            packedDataPos = 0;
            ReadRaw(packedData, 0, currentEntry.PackedDataSize);

            byte[] dtext = new byte[currentEntry.Dicsize];
            for (int i = 0; i < currentEntry.Dicsize; ++i) {
                dtext[i] = (byte)(' ');
            }
            DecodeStart();
            int dicsizeMinus1 = currentEntry.Dicsize - 1;
            int adjust = 256 - THRESHOLD;            
            decodedData = new byte[currentEntry.OriginalDataSize];
            decodedDataReadPos = 0;
            int decodedDataWritePos = 0;
            int decodedCount = 0;
            int dtextDataSize = 0;
            while (decodedCount < currentEntry.OriginalDataSize) {
                int c = DecodeC();
                if (c < 256) {
                    dtext[dtextDataSize++] = (byte)c;
                    if (dtextDataSize == currentEntry.Dicsize) {
                        System.Array.Copy(dtext, 0, decodedData, decodedDataWritePos, dtextDataSize);
                        decodedDataWritePos += dtextDataSize;
                        dtextDataSize = 0;
                    }
                    ++decodedCount;
                } else {
                    int matchlen = c - adjust;
                    int matchoff = DecodeP() + 1;
                    int matchpos = (dtextDataSize - matchoff) & dicsizeMinus1;
                    decodedCount += matchlen;
                    for (uint i = 0; i < matchlen; i++) {
                        c = dtext[(matchpos + i) & dicsizeMinus1];
                        dtext[dtextDataSize++] = (byte)c;
                        if (dtextDataSize == currentEntry.Dicsize) {
                            System.Array.Copy(dtext, 0, decodedData, decodedDataWritePos, dtextDataSize);
                            decodedDataWritePos += dtextDataSize;
                            dtextDataSize = 0;
                        }
                    }
                }
            }
            if (dtextDataSize != 0) {
                System.Array.Copy(dtext, 0, decodedData, decodedDataWritePos, dtextDataSize);
                decodedDataWritePos += dtextDataSize;
                dtextDataSize = 0;
            }
            dtext = null;
            packedData = null;
        }

        private const int LZHUFF4_DICBIT = 12;
        private const int LZHUFF5_DICBIT = 13;
        private const int LZHUFF6_DICBIT = 15;
        private const int LZHUFF7_DICBIT = 16;
        private int pbit, np;
        private ushort blocksize;
        
        private void DecodeStart()
        {
            switch (currentEntry.Dicbit) {
            case LZHUFF4_DICBIT:
            case LZHUFF5_DICBIT:
                pbit = 4;
                np = LZHUFF5_DICBIT + 1;
                break;
            case LZHUFF6_DICBIT:
                pbit = 5;
                np = LZHUFF6_DICBIT + 1;
                break;
            case LZHUFF7_DICBIT:
                pbit = 5;
                np = LZHUFF7_DICBIT + 1;
                break;
            default:
                System.Diagnostics.Debug.Assert(true);
                break;
            }
            InitGetBits();
            blocksize = 0;
        }

        private const int NT   = 19;
        private const int TBIT = 5;
        private const int MAXMATCH = 256;
        private const int NC = 255 + MAXMATCH + 2 - THRESHOLD; 
        private ushort[] cTable = new ushort[4096];
        private byte[] cLen = new byte[NC];
        private ushort[] left = new ushort[2*NC-1];
        private ushort[] right = new ushort[2*NC-1];

        private ushort DecodeC()
        {
            if (blocksize == 0) {
                blocksize = GetBits(16);
                ReadPtLen(NT, TBIT, 3);
                ReadCLen();
                ReadPtLen(np, pbit, -1);
            }
            blocksize--;
            ushort j = cTable[PeekBits(12)];
            if (j < NC) {
                FillBuf(cLen[j]);
                return j;
            }
            FillBuf(12);
            int mask = 1 << (16 - 1);
            do {
                if (0 != (GetBitbuf() & mask)) {
                    j = right[j];
                } else {
                    j = left[j];
                }
                mask >>= 1;
            } while (j >= NC && ((mask!=0) || (j != left[j])));
            FillBuf(cLen[j] - 12);
            return j;
        }

        private static int Min(int l, int r)
        {
            return (l < r) ? l : r;
        }

        private const int NPT = 128;
        private byte[] ptLen = new byte[NPT];
        private ushort[] ptTable = new ushort[256];

        private void ReadPtLen(int nn, int nbit, int iSpecial)
        {
            int n = GetBits(nbit);
            if (n == 0) {
                int c = GetBits(nbit);
                for (int i = 0; i < nn; i++) {
                    ptLen[i] = 0;
                }
                for (int i = 0; i < 256; i++) {
                    ptTable[i] = (ushort)c;
                }
                return;
            }
            {
                int i = 0;
                while (i < Min(n, NPT)) {
                    int c = PeekBits(3);
                    if (c != 7) {
                        FillBuf(3);
                    } else {
                        ushort mask = 1 << (16 - 4);
                        while (0 != (GetBitbuf() & mask)) {
                            mask >>= 1;
                            c++;
                        }
                        FillBuf(c - 3);
                    }
                    ptLen[i++] = (byte)c;
                    if (i == iSpecial) {
                        c = GetBits(2);
                        while (--c >= 0 && i < NPT) {
                            ptLen[i++] = 0;
                        }
                    }
                }
                while (i < nn) {
                    ptLen[i++] = 0;
                }
                MakeTable(nn, ptLen, 8, ptTable);
            }
        }

        private const int CBIT = 9;

        private void ReadCLen()
        {
            int n = GetBits(CBIT);
            if (n == 0) {
                int c = GetBits(CBIT);
                for (int i = 0; i < NC; i++) {
                    cLen[i] = 0;
                }
                for (int i = 0; i < 4096; i++) {
                    cTable[i] = (ushort)c;
                }
                return;
            }
            {
                int i = 0;
                while (i < Min(n, NC)) {
                    int c = ptTable[PeekBits(8)];
                    if (c >= NT) {
                        ushort mask = 1 << (16 - 9);
                        do {
                            if (0 != (GetBitbuf() & mask)) {
                                c = right[c];
                            } else {
                                c = left[c];
                            }
                            mask >>= 1;
                        } while (c >= NT && ((0 != mask) || c != left[c]));
                    }
                    FillBuf(ptLen[c]);
                    if (c <= 2) {
                        if (c == 0) {
                            c = 1;
                        } else if (c == 1) {
                            c = GetBits(4) + 3;
                        } else {
                            c = GetBits(CBIT) + 20;
                        }
                        while (--c >= 0) {
                            cLen[i++] = 0;
                        }
                    } else {
                        cLen[i++] = (byte)(c - 2);
                    }
                }
                while (i < NC) {
                    cLen[i++] = 0;
                }
                MakeTable(NC, cLen, 12, cTable);
            }
        }

        private ushort DecodeP()
        {
            ushort j = ptTable[PeekBits(8)];
            if (j < np) {
                FillBuf(ptLen[j]);
            } else {
                FillBuf(8);
                ushort mask = 1 << (16 - 1);
                do {
                    if (0 !=(GetBitbuf() & mask)) {
                        j = right[j];
                    } else {
                        j = left[j];
                    }
                    mask >>= 1;
                } while (j >= np && ((0 != mask) || j != left[j]));
                FillBuf(ptLen[j] - 8);
            }
            if (j != 0) {
                j = (ushort)((1 << (j - 1)) + GetBits(j - 1));
            }
            return j;
        }

        private void MakeTable(int nchar, byte[] bitLen, int tablebits, ushort[] table)
        {
            ushort[] count = new ushort[17];  // count of bitlen 
            ushort[] weight = new ushort[17]; // 0x10000ul >> bitlen 
            int    i;
            int avail = nchar;
            for (i = 1; i <= 16; i++) {
                count[i] = 0;
                weight[i] = (ushort)(1 << (16 - i));
            }
            for (i = 0; i < nchar; i++) {
                if (bitLen[i] > 16) {
                    throw new System.IO.InvalidDataException();
                }
                count[bitLen[i]]++;
            }
            ushort total = 0;
            ushort[] start = new ushort[17];
            for (i = 1; i <= 16; i++) {
                start[i] = total;
                total += (ushort)(weight[i] * count[i]);
            }
            if ((total & 0xffff) != 0 || tablebits > 16) {
                throw new System.IO.InvalidDataException();
            }
            int m = 16 - tablebits;
            for (i = 1; i <= tablebits; i++) {
                start[i] >>= m;
                weight[i] >>= m;
            }
            int j = start[tablebits + 1] >> m;
            int k = Min(1 << tablebits, 4096);
            if (j != 0) {
                for (i = j; i < k; i++) {
                    table[i] = 0;
                }
            }
            for (j = 0; j < nchar; j++) {
                k = bitLen[j];
                if (k == 0) {
                    continue;
                }
                int l = start[k] + weight[k];
                if (k <= tablebits) {
                    l = Min(l, 4096);
                    for (i = start[k]; i < l; i++) {
                        table[i] = (ushort)j;
                    }
                } else {
                    i = start[k];
                    if ((i >> m) > 4096) {
                        throw new System.IO.InvalidDataException();
                    }
                    int pos = i >> m;
                    TableOrLeftOrRight tlr = TableOrLeftOrRight.Table;
                    i <<= tablebits;
                    int n = k - tablebits;
                    while (--n >= 0) {
                        if (TableOrLeftOrRightGet(tlr, table, pos) == 0) {
                            right[avail] = left[avail] = 0;
                            TableOrLeftOrRightSet(tlr, table, pos, (ushort)(avail++));
                        }
                        if (0 !=(i & 0x8000)) {
                            pos = TableOrLeftOrRightGet(tlr, table, pos);
                            tlr = TableOrLeftOrRight.Right;
                        } else {
                            pos = TableOrLeftOrRightGet(tlr, table, pos);
                            tlr = TableOrLeftOrRight.Left;
                        }
                        i <<= 1;
                    }
                    TableOrLeftOrRightSet(tlr, table, pos, (ushort)j);
                }
                start[k] = (ushort)l;
            }
        }

        enum TableOrLeftOrRight
        {
            Table,
            Left,
            Right
        }

        private void TableOrLeftOrRightSet(TableOrLeftOrRight tlr, ushort[] table, int pos, ushort v)
        {
            switch (tlr) {
            case TableOrLeftOrRight.Table:
                table[pos] = v;
                return;
            case TableOrLeftOrRight.Left:
                left[pos] = v;
                return;
            case TableOrLeftOrRight.Right:
                right[pos] = v;
                return;
            }
        }

        private ushort TableOrLeftOrRightGet(TableOrLeftOrRight tlr, ushort[] table, int pos)
        {
            switch (tlr) {
            case TableOrLeftOrRight.Table:
                return table[pos];
            case TableOrLeftOrRight.Left:
                return left[pos];
            case TableOrLeftOrRight.Right:
                return right[pos];
            }
            System.Diagnostics.Debug.Assert(false);
            return 0;
        }

#region bitio
        private ushort bitbuf;
        private byte subbitbuf, bitcount;

        private ushort GetBitbuf()
        {
            return bitbuf;
        }

        /// <summary>
        /// Shift bitbuf n bits left, read n bits
        /// </summary>
        private void FillBuf(int n)
        {
            while (n > bitcount) {
                n -= bitcount;
                bitbuf = (ushort)((bitbuf << bitcount) + (subbitbuf >> (8 - bitcount)));
                subbitbuf = packedData[packedDataPos++]; // ReadRawOneByte();
                bitcount = 8;
            }
            bitcount -= (byte)n;
            bitbuf = (ushort)((bitbuf << n) + (subbitbuf >> (8 - n)));
            subbitbuf <<= n;
        }

        private ushort GetBits(int n)
        {
            int x = bitbuf >> (2 * 8 - n);
            FillBuf(n);
            return (ushort)x;
        }

        private void InitGetBits()
        {
            bitbuf = 0;
            subbitbuf = 0;
            bitcount = 0;
            FillBuf(2 * 8);
        }

        private ushort PeekBits(int n)
        {
            return (ushort)(bitbuf >> (2*8 - (n)));
        }
#endregion

#endregion
    }
}
